﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Net.Sockets;
using System.Net;
using System.Reflection;
using System.Diagnostics;
using System.Numerics;
using System.Threading;

// TODO: kolla upp network-protokoll kod (gammal C#)
// TODO: rensa upp koden
// TODO: fixa parsning av version från peers
// Done: https://en.bitcoin.it/wiki/Protocol_specification

// TODO: sätt upp listor enl. https://en.bitcoin.it/wiki/Protocol_rules
// TODO: hur skicka getblocks (första, när man inte vet hash)?

// TODO: kolla in http://code.google.com/p/bitcoinj/

namespace BitcoinLibrary
{
    public class Bitcoin
    {
        public class Block
        {
            public BigInteger Hash;
            public BigInteger PreviousBlockHash;
            public Block PreviousBlock = null;
        }

        public class BlockChainItem
        {
            public Block Block = null;
            public BlockChainItem PreviousItem = null;
        }

        public BlockChainItem BlockChain;

        public BlockChainItem LastBlock;

        public Dictionary<BigInteger, Block> blockChain = new Dictionary<BigInteger, Block>();
        public Dictionary<BigInteger, Block> orphanBlocks = new Dictionary<BigInteger, Block>();

        private Mutex peersMutex = new Mutex();
        private DateTime lastBlockTimestamp;
        private Random rand = new Random(DateTime.Now.Millisecond);

        public class Peer
        {
            private TcpClient client = null;
            private NetworkStream stream = null;
            private ProtocolHandler handler = null;

            public IPEndPoint Address { get; set; }
            public DateTime LastContactAt { get; set; }

            private ulong numberOfBlocks = 1000000; // TODO: set back to 0
            private uint version = 0;
            public bool verack = false;

            public delegate void GetAddrReceivedDelegate(Peer peer);
            public event GetAddrReceivedDelegate GetAddReceived;

            public delegate void AddrReceivedDelegate(Bitcoin_TimestampNetworkAddress address);
            public event AddrReceivedDelegate AddReceived;

            public delegate void ConnectedDelegate();
            public event ConnectedDelegate Connected;

            public delegate void GotBlockDelegate(Block block);
            public event GotBlockDelegate GotBlock;

            //public delegate Bitcoin_inv_vector[] ParseInvDelegate(Bitcoin_inv_vector[] vectors);
            //public event ParseInvDelegate ParseInv;

            public delegate void GotInvDelegate(Peer source, Bitcoin_inv_vector[] vectors);
            public event GotInvDelegate GotInv;

            private bool connected = false;

            private byte[] savedData = null;

            const int MAX_PAYLOAD = 50000;
            const int MAX_HEADER = 24;
            byte[] buffer = new byte[MAX_PAYLOAD + MAX_HEADER];

            // TODO: add failed connect delegate as well?

            public ulong NumberOfBlocks
            {
                get
                {
                    return numberOfBlocks;
                }
            }

            public uint Version
            {
                get
                {
                    return version;
                }
            }

            public bool Connect()
            {
                try
                {
                    handler = new ProtocolHandler();
                    handler.NewMessage += handler_NewMessage;
                    client = new TcpClient();
                    var res = client.BeginConnect(Address.Address, Address.Port, new AsyncCallback(ar =>
                        {
                            try
                            {
                                if (ar.IsCompleted)
                                {
                                    client.EndConnect(ar);
                                    stream = client.GetStream();
                                    connected = true;
                                    if (Connected != null)
                                    {
                                        Connected();
                                    }
                                    SendVersion();
                                }
                            }
                            catch
                            {
                                // TODO: send disconnected event
                                Debug.WriteLine("Failed to connect to " + Address.Address.ToString());
                            }
                        }), client);
                    return true;
                }
                catch
                {
                    return false;
                }
            }

            public bool IsConnected
            {
                get
                {
                    return connected;
                }
            }

            public void Disconnect()
            {
                stream.Close();
                client.Close();
                handler.NewMessage -= handler_NewMessage;
                stream = null;
                client = null;
                handler = null;
            }

            void handler_NewMessage(string command, byte[] payload)
            {
                Debug.WriteLine("handler_NewMessage: " + command);
                try
                {
                    switch (command)
                    {
                        case "version":
                            {
                                // Unpack version message
                                Bitcoin_version message = new Bitcoin_version();
                                message.Unpack(payload);

                                // Save peer info
                                numberOfBlocks = message.StartHeight;
                                LastContactAt = message.Timestamp;  // TODO: or use local time? or rename to ClientTime
                                version = message.Version;  // TODO: check version, take action if not supported

                                // Send verack
                                Bitcoin_header header = new Bitcoin_header();
                                header.Command = "verack";
                                header.PayloadLength = 0;
                                Write(header.Pack());
                            }
                            break;
                        case "verack":
                            {
                                // Set verack (received) flag
                                verack = true;

                                // Send getaddr to get list of peers from peer
                                SendGetaddr();
                            }
                            break;
                        case "getaddr":
                            if (GetAddReceived != null)
                            {
                                GetAddReceived(this);
                            }
                            break;
                        case "addr":
                            {
                                Bitcoin_addr message = new Bitcoin_addr();
                                //message.Payload = payload;  // TODO: set payload length?
                                message.Unpack(payload);

                                if (AddReceived != null)
                                {
                                    foreach (var peer in message.AddressList)
                                    {
                                        AddReceived(peer);
                                        // TODO: pass message to Bitcoin object? via event?
                                        // TODO: add new peers not in list to peers
                                    }
                                }
                            }
                            break;
                        case "inv":
                            {
                                //if (!unpackedInv)
                                {
                                    //unpackedInv = true;
                                    Bitcoin_inv message = new Bitcoin_inv();
                                    message.Unpack(payload);

                                    if (GotInv != null)
                                    {
                                        GotInv(this, message.Vectors);
                                    }
                                    //if (message.Vectors.Length == 1 &&
                                    //    message.Vectors[0].Type == Bitcoin_inv_vector.VectorType.MSG_BLOCK)// &&
                                    //    //TODO: test if top of my chain?) // special case 
                                    //{
                                    //    // An inv with a single hash containing our most recent unconnected block is a special inv,
                                    //    // it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to
                                    //    // the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head
                                    //    // block over and over again after each batch of 500 blocks, which is wasteful.

                                    //    //SendGetBlocks(new byte[][] { message.Vectors.)
                                    //    int why = 1;
                                    //}
                                    
                                    //if(ParseInv != null)
                                    //{
                                    //    var newVectors = ParseInv(message.Vectors);
                                    //    //for (int n = 0; n < newVectors.Length; n++)
                                    //    //{
                                    //    //    newVectors[n].hash = newVectors[n].hash.Reverse().ToArray();    // TODO: move handling of reversed hashes to message class! => not needed
                                    //    //}
                                    //    SendGetdata(this, newVectors);
                                    //}
                                    ////SendGetdata(this, message.Vectors);
                                }
                            }
                            break;
                        case "block":
                            {
                                Bitcoin_block message = new Bitcoin_block();
                                savedData = message.Unpack(payload);

                                // TODO: assert Pack of message equals payload?
                                var temp = message.Pack();
                                for (int n = 0; n < payload.Length; n++)
                                {
                                    if (payload[n] != temp[n])
                                    {
                                        Debug.WriteLine("error at " + n);
                                    }
                                }
                                byte[] data = new byte[80];
                                Array.Copy(payload, data, 80);
                                var sha256 = new SHA256Managed();
                                var hash = sha256.ComputeHash(sha256.ComputeHash(data)).Reverse().ToArray();

                                if (GotBlock != null)
                                {
                                    GotBlock(new Block
                                    {
                                        Hash = new BigInteger(message.Hash),
                                        PreviousBlockHash = new BigInteger(message.PreviousBlock.Reverse().ToArray()),
                                    });
                                }
                            }
                            break;
                        default:
                            Debug.WriteLine("Warning: unhandled message - " + command);
                            break;
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Failed with message");
                }
            }
            private static bool unpackedInv = false;
            public void Write(byte[] data)
            {
                stream.Write(data, 0, data.Length);
            }

            public void SendGetBlocks(byte[][] hashStart, byte[] hashStop)
            {
                Debug.WriteLine("Sending getblocks to " + Address.Address.ToString());

                Bitcoin_header_checksum header_msg = new Bitcoin_header_checksum();
                header_msg.Command = "getblocks";
                Bitcoin_getblocks packet = new Bitcoin_getblocks();
                packet.Version = 32100; // TODO: move version to separate class?
                packet.HashStop = hashStop;
                packet.HashStart = hashStart;
                packet.StartCount.Length = (ulong)hashStart.Length;
                byte[] payload = packet.Pack();
                header_msg.Payload = payload;
                header_msg.PayloadLength = (uint)payload.Length;
                byte[] head = header_msg.Pack();
                Write(head);
                Write(payload);
            }

            public void SendGetdata(Bitcoin_inv_vector[] vectors)
            {
                Debug.WriteLine("Sending getdata with " + vectors.Length + " vectors");

                Bitcoin_header_checksum header_msg = new Bitcoin_header_checksum();
                header_msg.Command = "getdata";
                Bitcoin_getdata packet = new Bitcoin_getdata();
                packet.Count.Length = (ulong)vectors.Length;    // TODO: make setting vectors (below) also set length
                packet.Vectors = vectors;
                byte[] payload = packet.Pack();
                header_msg.Payload = payload;
                header_msg.PayloadLength = (uint)payload.Length;
                byte[] head = header_msg.Pack();
                Write(head);
                Write(payload);
            }

            private void SendGetaddr()
            {
                Bitcoin_header_checksum header = new Bitcoin_header_checksum();
                header.Command = "getaddr";
                header.Payload = new byte[] { };
                Write(header.Pack());
            }

            public void SendVersion()
            {
                NetworkAddress na = new NetworkAddress();
                Bitcoin_header header_msg = new Bitcoin_header();
                header_msg.Command = "version";
                Bitcoin_version version_msg = new Bitcoin_version();
                version_msg.AddMe = na.GetBytes();
                version_msg.AddYou = na.GetBytes(Address.Address.ToString());
                //version_msg.StartHeight = TODO: take from Bitcoin class
                version_msg.StartHeight = (uint)numberOfBlocks; // TODO: why typecast?
                var versionData = version_msg.Pack();
                header_msg.PayloadLength = (uint)versionData.Length;
                var headerData = header_msg.Pack();
                Write(headerData);
                Write(versionData);

                Debug.WriteLine("SendVersion sent to " + Address.Address.ToString());
            }

            //public byte[] Read()
            public void Update()
            {
                try
                {
                    IAsyncResult asyncResult = stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(r =>
                    {
                        if (r.IsCompleted)
                        {
                            int length = stream.EndRead(r);
                            if (savedData != null)
                            {
                                savedData = null;
                                byte[] ret = new byte[savedData.Length + length];
                                Array.Copy(savedData, ret, savedData.Length);
                                Array.Copy(buffer, 0, ret, savedData.Length, length);
                                handler.Unpack(ret);
                            }
                            else
                            {
                                byte[] ret = new byte[length];
                                Array.Copy(buffer, ret, length);
                                handler.Unpack(ret);    // TODO: make handler stream based so that it works with packets cut off or larger than 50000 + 24 bytes!
                            }
// TODO: return array not parsed? append to previous array...
                        }
                    }), this);
                }
                catch (Exception ex)
                {
                    // TODO: pass on to Bitcoin 
                    connected = false;  // TODO: improve by removing peer from list?
                }                //int length = stream.Read(data, 0, data.Length);
                //if (length > 0)
                //{
                //    byte[] ret = new byte[length];
                //    Array.Copy(data, ret, length);
                //    //return ret;
                //    handler.Unpack(ret);
                //}
            }
        }

        public void TestHash()
        {
            // http://forum.bitcoin.org/index.php?topic=2461.msg56196#msg56196
            var sha256 = new SHA256Managed();
            byte[] payload = BigInteger.Parse("0000000150c549978a44e3ef671a4ed3f5b922195ebde0fbbccfdb5900e6827000000000c8a03dfb42fa04d47e9fbfd3e6afcb3d0df2b516eb86f4e64d09fe53cc397b6c4de61f4c1c069652a228e13d000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000", System.Globalization.NumberStyles.HexNumber).ToByteArray();
            byte[] genesis = BigInteger.Parse("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c", System.Globalization.NumberStyles.HexNumber).ToByteArray();
            byte[] genesis2 = new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, 0x29, 0xab, 0x5f, 0x49, 0xff, 0xff, 0x00, 0x1d, 0x1d, 0xac, 0x2b, 0x7c };
            byte[] pad1 = new byte[80];
            //Array.Copy(payload, genesis, 80);
            byte[] test1 = sha256.ComputeHash(genesis.Reverse().ToArray());
            byte[] test2 = sha256.ComputeHash(test1).Reverse().ToArray();

            byte[] padded = Enumerable.Repeat((byte)0, 128).ToArray();
            Array.Copy(payload, padded, 80);  // from start to nonce
            padded[80] = 0x80;
            padded[126] = 0x02;
            padded[127] = 0x80;

            var hash = sha256.ComputeHash(sha256.ComputeHash(padded));
        }

        /// <summary>
        /// List of all peers.
        /// </summary>
        private List<Peer> peers;

        public Peer[] Peers
        {
            get
            {
                return peers.ToArray();
            }
        }

        /// <summary>
        /// Indicates if a first getaddr has been received.
        /// If true, return with all peers.
        /// </summary>
        private bool firstGetaddr = false;

        // TODO: add collection of blocks

        public Bitcoin()
        {
            peers = new List<Peer>();
            Peer testPeer = new Peer
            {
                Address = new IPEndPoint(IPAddress.Parse("69.164.218.197"), 18333),
                LastContactAt = DateTime.MinValue,
            };
            peers.Add(testPeer);
            testPeer.GetAddReceived += testPeer_GetAddReceived;
            testPeer.AddReceived += testPeer_AddReceived;

            // TODO: change to real (not testnet) genesis block
            Block genesisBlock = new Block
            {
                //Hash = BigInteger.Parse("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008", System.Globalization.NumberStyles.HexNumber)
                Hash = new BigInteger(new byte[] { 0x00, 0x00, 0x00, 0x07, 0x19, 0x95, 0x08, 0xe3, 0x4a, 0x9f, 0xf8, 0x1e, 0x6e, 0xc0, 0xc4, 0x77, 0xa4, 0xcc, 0xcf, 0xf2, 0xa4, 0x76, 0x7a, 0x8e, 0xee, 0x39, 0xc1, 0x1d, 0xb3, 0x67, 0xb0, 0x08}/*.Reverse().ToArray()*/),
            };
            BlockChainItem genesis = new BlockChainItem
            {
                Block = genesisBlock,
                PreviousItem = null
            };
            BlockChain = genesis;
            LastBlock = genesis;
            blockChain.Add(genesisBlock.Hash, genesisBlock);

            lastBlockTimestamp = DateTime.MinValue;
        }

        public void Test()
        {
            SendGetBlocks();
        }


        void testPeer_AddReceived(Bitcoin_TimestampNetworkAddress address)
        {
            peersMutex.WaitOne();
            try
            {
                // TODO: improve comparison, only ip addresses should be compared =>
                var hits = peers.Where(peer => peer.Address.Address.ToString() == address.AddressList.EndPoint.Address.ToString()).ToArray();
                bool hasPeer = (hits.Length > 0);
                if (!hasPeer)
                {
                    var peer = new Peer
                    {
                        Address = address.AddressList.EndPoint,
                        LastContactAt = DateTime.Now,
                    };
                    peers.Add(peer);

                    //var hits2 = peers.Where(p => p.Address.Address.ToString() == address.AddressList.EndPoint.Address.ToString());
                    //Debug.Assert(hits2.Count() == 0);
                    //Debug.Assert(peers.GroupBy(p2 => p2.Address.Address).Count() == peers.Count);
                    peer.Connected += new Peer.ConnectedDelegate(() =>
                    {
                        Debug.WriteLine("Peer " + peer.Address.Address.ToString() + " connected!");
                        //peersMutex.WaitOne();
                        //try
                        //{
                        //    //peers.Add(peer);
                        //}
                        //finally
                        //{
                        //    //peersMutex.ReleaseMutex();
                        //}
                        peer.GetAddReceived += testPeer_GetAddReceived;
                        peer.AddReceived += testPeer_AddReceived;
                        peer.GotBlock += peer_GotBlock;
                        //peer.ParseInv += peer_ParseInv;
                        peer.GotInv += peer_GotInv;
                        peer.SendVersion();
                    });

                    // Startup peer
                    // TODO: put into members?
                    if (peer.Connect())
                    {
                        //peers.Add(peer);
                        //peer.GetAddReceived += testPeer_GetAddReceived;
                        //peer.AddReceived += testPeer_AddReceived;
                        //peer.SendVersion();
                        // TODO: remove peer?
                    }
                }
                else
                {
                    // TODO: need to update peer?
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Failed to add received peer: " + ex.Message);
            }
            finally
            {
                peersMutex.ReleaseMutex();
            }
            // TODO: check that we do not have address already!!! peers.Where(peer => peer.Address.Address == address.AddressList.)
            //peers.Add(new Peer
            //    {

            //    });
        }

        void peer_GotInv(Peer source, Bitcoin_inv_vector[] vectors)
        {
            if (vectors.Length == 1 &&
                vectors[0].Type == Bitcoin_inv_vector.VectorType.MSG_BLOCK &&
               new BigInteger(vectors[0].hash.Reverse().ToArray()) == blockChain.Last().Key) // special case 
            {
                // An inv with a single hash containing our most recent unconnected block is a special inv,
                // it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to
                // the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head
                // block over and over again after each batch of 500 blocks, which is wasteful.

                //SendGetBlocks(new byte[][] { message.Vectors.)
                int why = 1;
                SendGetBlocks(blockChain.Last().Key.ToByteArray()); // TODO: reverse?
            }
            else
            {
                var newVectors = ParseInv(vectors);
                if (newVectors.Length > 0)
                {
                    source.SendGetdata(newVectors);
                }
            }
        }

        Bitcoin_inv_vector[] ParseInv(Bitcoin_inv_vector[] vectors)
        {
            List<Bitcoin_inv_vector> newVectors = new List<Bitcoin_inv_vector>();
            foreach (var v in vectors)
            {
                if (v.Type == Bitcoin_inv_vector.VectorType.MSG_BLOCK &&
                    !blockChain.ContainsKey(new BigInteger(v.hash.Reverse().ToArray())))    // TODO: reverse?
                {
                    // not known => add to return array
                    newVectors.Add(v);
                }
            }
            return newVectors.ToArray();
        }

        void peer_GotBlock(Bitcoin.Block block)
        {
            if (blockChain.ContainsKey(block.Hash))
            {
                return;
            }
            if (orphanBlocks.ContainsKey(block.Hash))
            {
                return;
            }

            // TODO: find using prev
            if (blockChain.ContainsKey(block.PreviousBlockHash))
            {
                // Previous block found => attach block
                block.PreviousBlock = blockChain[block.PreviousBlockHash];
                blockChain.Add(block.Hash, block);
                Debug.WriteLine("adding block " + block.Hash + " (" + block.PreviousBlockHash + ") to chain");

                // TODO: check if any orphan blocks point to newly added block?
                CheckAndConnectAnyOrphans(block);
            }
            //else if (orphanBlocks.ContainsKey(block.PreviousBlockHash))
            //{
            //    // Previous block found => attach block and add to orphans
            //    block.PreviousBlock = orphanBlocks[block.PreviousBlockHash];
            //    orphanBlocks.Add(block.Hash, block);
            //}
            else
            {
                // No connection => add block to orphans
                orphanBlocks.Add(block.Hash, block);

                if (blockChain.Where(b => b.Key == block.PreviousBlockHash).Count() > 0)
                {
                    int hit = 1;
                }
            }

            /*
    // Preliminary checks
    if (!pblock->CheckBlock())
        return error("ProcessBlock() : CheckBlock FAILED");

    // If don't already have its previous block, shunt it off to holding area until we get it
    if (!mapBlockIndex.count(pblock->hashPrevBlock))
    {
        printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
        CBlock* pblock2 = new CBlock(*pblock);
        mapOrphanBlocks.insert(make_pair(hash, pblock2));
        mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));

        // Ask this guy to fill in what we're missing
        if (pfrom)
            pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));
        return true;
    }

    // Store to disk
    if (!pblock->AcceptBlock())
        return error("ProcessBlock() : AcceptBlock FAILED");

    // Recursively process any orphan blocks that depended on this one
    vector<uint256> vWorkQueue;
    vWorkQueue.push_back(hash);
    for (int i = 0; i < vWorkQueue.size(); i++)
    {
        uint256 hashPrev = vWorkQueue[i];
        for (multimap<uint256, CBlock*>::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev);
             mi != mapOrphanBlocksByPrev.upper_bound(hashPrev);
             ++mi)
        {
            CBlock* pblockOrphan = (*mi).second;
            if (pblockOrphan->AcceptBlock())
                vWorkQueue.push_back(pblockOrphan->GetHash());
            mapOrphanBlocks.erase(pblockOrphan->GetHash());
            delete pblockOrphan;
        }
        mapOrphanBlocksByPrev.erase(hashPrev);
    }

    printf("ProcessBlock: ACCEPTED\n");
    return true;

            */
        }

        /// <summary>
        /// Checks if any orphan is pointing to a given block. If true, move it to block chain.
        /// </summary>
        /// <param name="block"></param>
        private void CheckAndConnectAnyOrphans(Bitcoin.Block block)
        {
            foreach (var orphan in orphanBlocks)
            {
                if (orphan.Value.PreviousBlockHash == block.Hash)
                {
                    // Found orphan 
                    orphan.Value.PreviousBlock = block;
                    orphanBlocks.Remove(orphan.Key);
                    blockChain.Add(orphan.Key, orphan.Value);
                    CheckAndConnectAnyOrphans(orphan.Value);    // TODO: do not use recursion
                    return;
                }
            }
        }

        void testPeer_GetAddReceived(Peer peer)
        {
            // TODO: how to decide with how many peers to reply with?
        }

        /// <summary>
        /// Connect to peers.
        /// </summary>
        public void Connect()
        {
            foreach (Peer peer in peers)
            {
                peer.Connect();
            }
        }

        public void GetBlocks()
        {
            foreach (Peer peer in peers)
            {
                //if(peer.NumberOfBlocks > MY KeyNumber OF BLOCKS)
                {
                    //peer.sendGetBlocks();
                    //break;
                }
            }
        }

        public static char[] StringToCharArray(String hex)
        {
            int NumberChars = hex.Length;
            char[] bytes = new char[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
            {
                bytes[i / 2] = (char)Convert.ToByte(hex.Substring(i, 2), 16);
            }
            return bytes;
        }

        // TODO: testa!!!!!!!!!
        private void SendGetBlocks()
        {
            SendGetBlocks(Enumerable.Range(0, 32).Select(i => (byte)0).ToArray());
            //var possiblePeers = peers.Where(p => p.NumberOfBlocks > (ulong)blockChain.Count && p.IsConnected).ToArray();
            //if (possiblePeers.Length > 5)
            //{
            //    int index = rand.Next(0, possiblePeers.Length);
            //    Peer peer = possiblePeers[index];

            //    Debug.WriteLine("Sending getblocks to " + peer.Address.Address.ToString());

            //    //Peer peer = peers[1];   // TODO: all peers?
            //    //foreach (Peer peer in Peers.Where(p => p.IsConnected))
            //    //foreach (Peer peer in new Peer[] { peers[1], peers[2] })
            //    {
            //        Bitcoin_header_checksum header_msg = new Bitcoin_header_checksum();
            //        header_msg.Command = "getblocks";
            //        Bitcoin_getblocks packet = new Bitcoin_getblocks();
            //        packet.Version = 32100; // TODO: move version to separate class?
            //        packet.HashStop = Enumerable.Range(0, 32).Select(i => (byte)0).ToArray();
            //        if (blockChain.Count == 1)
            //        {
            //            packet.HashStart = new byte[][] { blockChain.Last().Value.Hash.ToByteArray() }; // genesis
            //            packet.StartCount.Length = 1;
            //        }
            //        else
            //        {
            //            // See http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Peer.java
            //            packet.HashStart = new byte[][] 
            //            { 
            //                blockChain.First().Value.Hash.ToByteArray(), // genesis
            //                blockChain.Last().Value.Hash.ToByteArray() // last block hash
            //            };
            //            packet.StartCount.Length = (uint)packet.HashStart.Length;
            //        }
            //        byte[] payload = packet.Pack();
            //        header_msg.Payload = payload;
            //        header_msg.PayloadLength = (uint)payload.Length;
            //        byte[] head = header_msg.Pack();
            //        peer.Write(head);
            //        peer.Write(payload);
            //    }
            //}
        }

        private void SendGetBlocks(byte[] hashStop)
        {
            var possiblePeers = peers.Where(p => p.NumberOfBlocks > (ulong)blockChain.Count && p.IsConnected).ToArray();
            if (possiblePeers.Length > 5)
            {
                int index = rand.Next(0, possiblePeers.Length);
                Peer peer = possiblePeers[index];

                Debug.WriteLine("Sending getblocks to " + peer.Address.Address.ToString());

                //Peer peer = peers[1];   // TODO: all peers?
                //foreach (Peer peer in Peers.Where(p => p.IsConnected))
                //foreach (Peer peer in new Peer[] { peers[1], peers[2] })
                {
                    Bitcoin_header_checksum header_msg = new Bitcoin_header_checksum();
                    header_msg.Command = "getblocks";
                    Bitcoin_getblocks packet = new Bitcoin_getblocks();
                    packet.Version = 32100; // TODO: move version to separate class?
                    packet.HashStop = hashStop;
                    if (blockChain.Count == 1)
                    {
                        packet.HashStart = new byte[][] { blockChain.Last().Value.Hash.ToByteArray() }; // genesis
                        packet.StartCount.Length = 1;
                    }
                    else
                    {
                        // See http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Peer.java
                        packet.HashStart = new byte[][] 
                        { 
                            blockChain.Last().Value.Hash.ToByteArray(), // last block hash
                            blockChain.First().Value.Hash.ToByteArray() // genesis
                        };
                        packet.StartCount.Length = (uint)packet.HashStart.Length;
                    }
                    byte[] payload = packet.Pack();
                    header_msg.Payload = payload;
                    header_msg.PayloadLength = (uint)payload.Length;
                    byte[] head = header_msg.Pack();
                    peer.Write(head);
                    peer.Write(payload);
                }
            }
        }
        public void SendVersion()
        {
            NetworkAddress na = new NetworkAddress();
            foreach (Peer peer in peers)
            {
                Bitcoin_header header_msg = new Bitcoin_header();
                header_msg.Command = "version";
                Bitcoin_version version_msg = new Bitcoin_version();
                version_msg.AddMe = na.GetBytes();
                version_msg.AddYou = na.GetBytes(peer.Address.Address.ToString());
                // TODO: set according to list of blocks length    version_msg.StartHeight
                //version_msg.StartHeight = TODO: take from Bitcoin class
                version_msg.StartHeight = (uint)peer.NumberOfBlocks;    // TODO: why typecast?
                var versionData = version_msg.Pack();
                header_msg.PayloadLength = (uint)versionData.Length;
                var headerData = header_msg.Pack();
                peer.Write(headerData);
                peer.Write(versionData);
            }
        }

        public void UpdatePeers()
        {
            foreach (Peer peer in Peers)
            {
                if (peer.IsConnected)
                {
                    peer.Update();
                }
            }

            // Check for duplicates
            int duplicates = Peers.GroupBy(g => g.Address.Address.ToString()).Where(g2 => g2.Count() > 1).Count();
            Debug.Assert(duplicates == 0);

            // Blocks still missing in block chain?
            if(((ulong)blockChain.Count < GetMaxNumberOfBlocks()) && 
                (DateTime.Now.Subtract(lastBlockTimestamp).TotalSeconds > 5))
            {
                lastBlockTimestamp = DateTime.Now;
                SendGetBlocks();
            }
        }

        public ulong GetMaxNumberOfBlocks()
        {
            return Peers.Max(p => p.NumberOfBlocks);
        }

        public void Test2()
        {
            //byte[] data = new byte[] { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
            //byte[] result;
            //SHA256 shaM = new SHA256Managed();
            //result = shaM.ComputeHash(data);
            //RIPEMD160 ripmed = new RIPEMD160Managed();
            //result = ripmed.ComputeHash(result);
            //Console.WriteLine("");

            //VersionTest test = new VersionTest();
            //test.Timestamp = 123123;
            //byte[] testData = test.Pack();

            Bitcoin_header header_msg = new Bitcoin_header();
            header_msg.Command = "version";
            var packed = header_msg.Pack();

            ProtocolHandler handler = new ProtocolHandler();
            handler.NewMessage += new ProtocolHandler.NewMessageDelegate(handler_NewMessage);
            //handler.Unpack(packed);
            handler.Unpack(MessageVersion.testPacket);  // test with test packet

            TcpClient client = new TcpClient("69.164.218.197", 18333);
            Byte[] data = MessageVersion.testPacket;// System.Text.Encoding.ASCII.GetBytes(message);

            data = new MessageVersion().GetPayload("69.164.218.197");
            NetworkStream stream = client.GetStream();
            stream.Write(data, 0, data.Length);

            // Buffer to store the response bytes.
            data = new Byte[256];

            // String to store the response ASCII representation.
            String responseData = String.Empty;

            // Read the first batch of the TcpServer response bytes.
            Int32 bytes = stream.Read(data, 0, data.Length);
            responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
            Console.WriteLine("Received: {0}", responseData);

            // Close everything.
            stream.Close();
            client.Close();

            handler.Unpack(data);
        }

        void handler_NewMessage(string command, byte[] payload)
        {
            string cmd = command;
            //Debug.WriteLine("hejsan = " + cmd);
            switch (command)
            {
                case "version":
                    Bitcoin_version version = new Bitcoin_version();
                    version.Unpack(payload);
                    break;
                case "verack":
                    break;
                default:
                    throw new Exception("Unsupported protocol command '" + command + "' detected.");
            }
        }
    }

    /// <summary>
    /// https://en.bitcoin.it/wiki/Protocol_Specification#version
    /// </summary>
    public class MessageHeader
    {
        public UInt32 magic = 0xFABFB5DA;   // testnet
        //public UInt32 magic = 0xF9BEB4D9;
        private byte[] command = new byte[12];
        //private UInt32 length = 0;
        private static Random random = new Random(DateTime.Now.Millisecond);

        //private UInt32 checksum = 0;
        public byte[] payload;

        public string Command
        {
            get
            {
                return Encoding.ASCII.GetString(command);
            }
            set
            {
                byte[] bytes = Encoding.ASCII.GetBytes(value);
                command = new byte[12];
                bytes.CopyTo(command, 0);
            }
        }

        /// <summary>
        /// First 4 bytes of sha256(sha256(payload)) (not included in version or verack) 
        /// </summary>
        public UInt32 Checksum
        {
            get
            {
                var sha256 = new SHA256Managed();
                var hash = sha256.ComputeHash(sha256.ComputeHash(payload));
                return BitConverter.ToUInt32(hash, 0);
            }
        }

        public UInt32 Length
        {
            get
            {
                return (UInt32)payload.Length;
            }
        }

        public byte[] GetShortHeader()
        {
            List<byte> header = new List<byte>();
            header.AddRange(BitConverter.GetBytes(magic).Reverse());
            header.AddRange(command);
            header.AddRange(BitConverter.GetBytes(Length));
            return header.ToArray();
        }

        /// <summary>
        /// method for converting a System.DateTime value to a UNIX Timestamp
        /// </summary>
        /// <param name="value">date to convert</param>
        /// <returns></returns>
        public static UInt32 ConvertToTimestamp(DateTime value)
        {
            // Create Timespan by subtracting the value provided from the Unix Epoch
            TimeSpan span = (value - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());

            // return the total seconds (which is a UNIX timestamp)
            return (UInt32)span.TotalSeconds;
        }

        public static UInt64 CreateNonce()
        {
            return (UInt64)(random.NextDouble() * UInt64.MaxValue);
        }
    }

    public class NetworkAddress : ProtocolMessage
    {
        public byte[] GetBytes()
        {
            List<byte> networkAddress = new List<byte>();
            IPAddress address = Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).First();
            networkAddress.AddRange(BitConverter.GetBytes((UInt32)1)); // NODE_NETWORK services
            byte[] addrBytes = address.GetAddressBytes();
            networkAddress.AddRange(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF });
            networkAddress.AddRange(addrBytes);
            //const UInt16 port = 8333;
            const UInt16 port = 18333;
            networkAddress.AddRange(BitConverter.GetBytes(port));
            return networkAddress.ToArray();
        }

        public byte[] GetBytes(string host) // TODO: add port?
        {
            List<byte> networkAddress = new List<byte>();
            IPAddress address = IPAddress.Parse(host);  // TODO: better way?
            networkAddress.AddRange(BitConverter.GetBytes((UInt32)1)); // NODE_NETWORK services
            byte[] addrBytes = address.GetAddressBytes();
            networkAddress.AddRange(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF });
            networkAddress.AddRange(addrBytes);
            //const UInt16 port = 8333;
            const UInt16 port = 18333;
            networkAddress.AddRange(BitConverter.GetBytes(port));
            return networkAddress.ToArray();
        }

        [ProtocolMember]
        public UInt64 Services = 1;

        [ProtocolMember]
        public byte[] Address = new byte[16];

        [ProtocolMember]
        public byte[] port = new byte[] { 0, 0 };

        public UInt16 Port
        {
            get
            {
                return BitConverter.ToUInt16(new byte[] { port[1], port[0] }, 0);
            }
            set
            {
                var bytes = BitConverter.GetBytes(value);
                port[0] = bytes[1];
                port[1] = bytes[0];
            }
        }

        public IPEndPoint EndPoint
        {
            get
            {
                return new IPEndPoint(new IPAddress(new byte[] { Address[12], Address[13], Address[14], Address[15] }), Port);
            }
        }
    }

    public class ProtocolMemberAttribute : Attribute
    {
        public ProtocolMemberAttribute()
        {
            Order = -1; // undefined
        }

        public int Order { get; set; }
    }

    public class ProtocolMessage
    {
        protected static Random random = new Random(DateTime.Now.Millisecond);

        private static MemberInfo[] GetAttributes(Type type)
        {
            List<Tuple<int, MemberInfo>> memberInfos = new List<Tuple<int, MemberInfo>>();
            foreach (MemberInfo mi in type.GetMembers())
            {
                foreach (var attr in mi.GetCustomAttributes(typeof(ProtocolMemberAttribute), true))
                {
                    int order = (attr as ProtocolMemberAttribute).Order;
                    if (mi.MemberType == MemberTypes.Field ||
                        mi.MemberType == MemberTypes.Property)
                    {
                        memberInfos.Add(new Tuple<int, MemberInfo>(order, mi));
                    }
                }
            }

            return memberInfos.OrderBy(mi => mi.Item1).Select(mi => mi.Item2).ToArray();
        }

        // TODO: make member instead of static?
        protected virtual void Pack(List<byte> message)
        {
            PackInternal(this, message);
        }

        private static void PackInternal(object obj, List<byte> message)
        {
            List<Tuple<int, object>> protocolMembers = new List<Tuple<int, object>>();
            foreach (MemberInfo mi in obj.GetType().GetMembers())
            {
                foreach (var attr in mi.GetCustomAttributes(typeof(ProtocolMemberAttribute), true))
                {
                    if (mi.MemberType == MemberTypes.Field)
                    {
                        //Debug.WriteLine(mi.Name);
                        FieldInfo fi = obj.GetType().GetField(mi.Name);
                        var value = fi.GetValue(obj);
                        protocolMembers.Add(new Tuple<int, object>((attr as ProtocolMemberAttribute).Order, value));
                        //AddBytes(message, value);
                    }
                    else if (mi.MemberType == MemberTypes.Property)
                    {
                        //Debug.WriteLine(mi.Name);
                        PropertyInfo pi = obj.GetType().GetProperty(mi.Name);
                        var value = pi.GetValue(obj, null);
                        protocolMembers.Add(new Tuple<int, object>((attr as ProtocolMemberAttribute).Order, value));
                        //AddBytes(message, value);
                    }
                }
            }

            // Add data according to order
            foreach (var pm in protocolMembers.OrderBy(x => x.Item1))
            {
                AddBytes(message, pm.Item2);
            }
        }

        private static void AddBytes(List<byte> message, object value)
        {
            if (value is ProtocolValue)
            {
                message.AddRange((value as ProtocolValue).GetBytes());
            }
            else if (value.GetType().Name == "Byte[]")
            {
                message.AddRange((value as byte[]));
            }
            else if (value.GetType() == typeof(UInt32))
            {
                message.AddRange(BitConverter.GetBytes((UInt32)value));
            }
            else if (value.GetType() == typeof(UInt64))
            {
                message.AddRange(BitConverter.GetBytes((UInt64)value));
            }
            else if (value.GetType() == typeof(char))
            {
                message.Add((byte)(char)value);
            }
            else if (value is ProtocolMessage)
            {
                message.AddRange((value as ProtocolMessage).Pack());
            }
            else if (value.GetType().Name == "Char[]")
            {
                var array = (value as char[]).Select(v => (byte)v).ToArray();
                message.AddRange(array);
            }
            else if (value is Array)
            {
                Array array = (Array)value;
                foreach (var item in array)
                {
                    AddBytes(message, item);
                }
            }
            else
            {
                // TODO: handle error
                throw new Exception("Unhandled datatype detected: " + value.GetType().Name);
            }
        }

        public byte[] Pack()
        {
            List<byte> message = new List<byte>();
            //Pack(this, message);
            Pack(message);
            return message.ToArray();
        }

        protected virtual void Unpack(byte[] message, ref int index)
        {
            List<Tuple<int, MemberInfo>> protocolMembers = new List<Tuple<int, MemberInfo>>();
            //foreach (MemberInfo mi in GetAttributes(obj.GetType()))
            //{
            foreach (MemberInfo mi in this.GetType().GetMembers())
            {
                foreach (var attr in mi.GetCustomAttributes(typeof(ProtocolMemberAttribute), true))
                {
                    if (mi.MemberType == MemberTypes.Field)
                    {
                        FieldInfo fi = this.GetType().GetField(mi.Name);
                        //var value = fi.GetValue(obj);
                        protocolMembers.Add(new Tuple<int, MemberInfo>((attr as ProtocolMemberAttribute).Order, mi));
                        //ParseBytes(message, ref index, ref value);
                        //fi.SetValue(obj, value);
                    }
                    else if (mi.MemberType == MemberTypes.Property)
                    {
                        PropertyInfo pi = this.GetType().GetProperty(mi.Name);
                        //var value = pi.GetValue(obj, null);
                        protocolMembers.Add(new Tuple<int, MemberInfo>((attr as ProtocolMemberAttribute).Order, mi));
                        //ParseBytes(message, ref index, ref value);
                        //pi.SetValue(obj, value, null);
                    }
                }
            }

            // Unpack data according to order
            foreach (var pm in protocolMembers.OrderBy(x => x.Item1))
            {
                if (pm.Item2.MemberType == MemberTypes.Field)
                {
                    FieldInfo fi = this.GetType().GetField(pm.Item2.Name);
                    var value = fi.GetValue(this);
                    ParseBytes(message, ref index, ref value);
                    fi.SetValue(this, value);
                }
                else if (pm.Item2.MemberType == MemberTypes.Property)
                {
                    PropertyInfo pi = this.GetType().GetProperty(pm.Item2.Name);
                    var value = pi.GetValue(this, null);
                    ParseBytes(message, ref index, ref value);
                    pi.SetValue(this, value, null);
                }
                else
                {
                    throw new Exception("Unhandled member type detected");
                }
            }
#if false
            foreach (MemberInfo mi in obj.GetType().GetMembers()) // TODO: add similiar to handle private members as well: BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField | BindingFlags.SetProperty))
            {
                foreach (var attr in mi.GetCustomAttributes(typeof(ProtocolMemberAttribute), true))
                {
                    if (mi.MemberType == MemberTypes.Field)
                    {
                        FieldInfo fi = obj.GetType().GetField(mi.Name);
                        var value = fi.GetValue(obj);
                        ParseBytes(message, ref index, ref value);
                        fi.SetValue(obj, value);
                        Console.WriteLine(mi.Name + " = " + obj.GetType().GetField(mi.Name).GetValue(obj));
                    }
                    else if (mi.MemberType == MemberTypes.Property)
                    {
                        PropertyInfo pi = obj.GetType().GetProperty(mi.Name);
                        var value = pi.GetValue(obj, null);
                        ParseBytes(message, ref index, ref value);
                        pi.SetValue(obj, value, null);
                        Console.WriteLine(mi.Name + " = " + obj.GetType().GetProperty(mi.Name).GetValue(obj, null));
                    }
                }
            }
#endif
        }

        private void ParseBytes(byte[] message, ref int index, ref object value)
        {
            if (value.GetType().Name == "Byte[]")
            {
                int len = (value as byte[]).Length;
                Array.Copy(message, index, (value as byte[]), 0, len);
                index += len;
            }
            else if (value.GetType() == typeof(UInt32))
            {
                value = BitConverter.ToUInt32(message, index);
                index += sizeof(UInt32);
            }
            else if (value.GetType() == typeof(UInt16))
            {
                value = BitConverter.ToUInt16(message, index);
                index += sizeof(UInt16);
            }
            else if (value.GetType() == typeof(UInt64))
            {
                value = BitConverter.ToUInt64(message, index);
                index += sizeof(UInt64);
            }
            else if (value.GetType() == typeof(byte))
            {
                value = message[index];
                index += sizeof(byte);
            }
            else if (value.GetType() == typeof(char))
            {
                value = (char)message[index];
                index += 1;
            }
            else if (value is ProtocolMessage)
            {
                (value as ProtocolMessage).Unpack(message, ref index);
            }
            else if (value is Array)
            {
                Array array = (Array)value;
                for (int n = 0; n < array.Length; n++)
                {
                    //Unpack(item, message, ref index);
                    object obj = array.GetValue(n);
                    ParseBytes(message, ref index, ref obj);
                    array.SetValue(obj, n);
                }
            }
            else
            {
                throw new Exception("Unsupported protocol member '" + value.GetType().Name + "' detected.");
            }
        }

        /// <summary>
        /// Unpacks payload byte array into a protocol message.
        /// </summary>
        /// <param name="payload"></param>
        /// <returns>Number of bytes processed.</returns>
        public byte[] Unpack(byte[] payload)
        {
            int index = 0;
            try
            {
                Unpack(payload, ref index);

            }
            catch (Exception)
            {
                throw;
            } 
            if (index != payload.Length)
            {
                if (payload.Length > index)
                {
                    Debug.WriteLine("Warning: possible payload protocol message length mismatch.");
                    byte[] savedData = new byte[payload.Length - index];
                    Array.Copy(payload, index, savedData, 0, savedData.Length);
                    return savedData;

                }
                else
                {
                    throw new Exception("Payload protocol message length mismatch.");
                }
            }
            return null;
        }
    }

    public class Bitcoin_var_int : ProtocolMessage
    {
        public delegate void ChangedDelegate(UInt64 length);
        public event ChangedDelegate Changed;

        [ProtocolMember]
        public char FirstLength = (char)0;

        public UInt64 Length
        {
            get
            {
                return FirstLength;
            }
            set
            {
                // Done: implement support for larger values
                // Debug.Assert(value < 0xfd);
                FirstLength = (char)value;
            }
        }

        // TODO: override Pack() !!!!
        protected override void Pack(List<byte> message)
        {
            //base.Pack(message);
            if (Length < 0xFD)
            {
                message.Add((byte)Length);
            }
            else if (Length <= 0xFFFF)
            {
                message.Add(0xFD);
                message.AddRange(BitConverter.GetBytes((UInt16)Length));
            }
            else if (Length <= 0xFFFFFFFF)
            {
                message.Add(0xFE);
                message.AddRange(BitConverter.GetBytes((UInt32)Length));
                //ulong remainder = (Length - 0xFE) / 256;
                //byte[] bytes = BitConverter.GetBytes(remainder);
                //message.Add(bytes[0]);
                //message.Add(bytes[1]);
                //message.Add(bytes[2]);
            }
            else
            {
                message.Add(0xFF);
                message.AddRange(BitConverter.GetBytes((UInt64)Length));
                //ulong remainder = (Length - 0xFF) / 256;
                //byte[] bytes = BitConverter.GetBytes(remainder);
                //message.Add(bytes[0]);
                //message.Add(bytes[1]);
                //message.Add(bytes[2]);
                //message.Add(bytes[3]);
                //message.Add(bytes[4]);
                //message.Add(bytes[5]);
                //message.Add(bytes[6]);
            }
        }

        protected override void Unpack(byte[] message, ref int index)
        {
            base.Unpack(message, ref index);
            Length = FirstLength;
            if (FirstLength >= 0xFD)
            {
                switch (FirstLength)
                {
                    case (char)0xFD:
                        {
                            UInt16 nextLength = BitConverter.ToUInt16(new byte[] { message[index], message[index + 1] }, 0);
                            // TODO: not correct? Length += nextLength;
                            Length = nextLength;
                            index += 2;
                        }
                        break;
                    case (char)0xFE:
                        {
                            UInt32 nextLength = BitConverter.ToUInt32(new byte[] { message[index], message[index + 1], message[index + 2], message[index + 3] }, 0);
                            //Length += nextLength;
                            Length = nextLength;
                            index += 4;
                        }
                        break;
                    case (char)0xFF:
                        {
                            UInt64 nextLength = BitConverter.ToUInt32(new byte[] { message[index], message[index + 1], message[index + 2], message[index + 3],
                                message[index + 4], message[index + 5], message[index + 6], message[index + 7]}, 0);
                            //Length += nextLength;
                            Length = nextLength;
                            index += 8;
                        }
                        break;
                    default:
                        throw new Exception("unsupported var_int");
                }
            }
            if (Changed != null)
            {
                Changed(Length);
            }
        }
    }

    public class ProtocolHandler
    {
        //private List<byte> incoming = new List<byte>();
        public readonly byte[] MAGIC = new byte[] { 0xFA, 0xBF, 0xB5, 0xDA };
        private byte[] command = new byte[12];
        public UInt32 Length;
        private byte[] length = new byte[sizeof(UInt32)];
        public byte[] Payload;
        private int index = 0;
        private bool includeChecksum;
        private byte[] checksum = new byte[sizeof(UInt32)];
        public UInt32 Checksum;

        public delegate void NewMessageDelegate(string command, byte[] payload);
        public event NewMessageDelegate NewMessage;

        public string Command
        {
            get
            {
                string cmd = Encoding.ASCII.GetString(command);
                int index = cmd.IndexOf((char)0);
                if (index != -1)
                {
                    cmd = cmd.Substring(0, index);
                }
                return cmd;
            }
            set
            {
                byte[] bytes = Encoding.ASCII.GetBytes(value);
                command = new byte[12];
                bytes.CopyTo(command, 0);
            }
        }

        // TODO: rename to parse?
        public void Unpack(byte[] data)
        {
            lock (this)
            {
                foreach (byte b in data)
                {
                    Unpack(b);
                }
            }
        }

        // TODO: rename to parse?
        public void Unpack(byte b)
        {
            if (index < MAGIC.Length)
            {
                // Build and check magic code
                if (b != MAGIC[index]) // mismatch
                {
                    index = 0;  // re-start 
                }
                else
                {
                    index++;
                }
            }
            else if (index < MAGIC.Length + command.Length)
            {
                command[index - MAGIC.Length] = b;
                index++;
            }
            else if (index < MAGIC.Length + command.Length + sizeof(UInt32))
            {
                int lengthIndex = index - MAGIC.Length - command.Length;
                length[lengthIndex] = b;
                index++;
                if (lengthIndex == sizeof(UInt32) - 1)
                {
                    Length = BitConverter.ToUInt32(length, 0);

                    // TODO: check that Length is not too large
                    if (Length > 50000) // TODO: change to MAX....
                    {
                        index = 0;
                        return;
                    }
                    Payload = new byte[Length];

                    if (Length == 0)
                    {
                        // Finished!
                        if (NewMessage != null)
                        {
                            NewMessage(Command, Payload);
                        }
                        index = 0;  // re-start
                    }

                    // Check: 4  checksum  uint32_t  First 4 bytes of sha256(sha256(payload)) (not included in version or verack)  
                    switch (Command)
                    {
                        case "version":
                        case "verack":
                            includeChecksum = false;
                            break;
                        default:
                            includeChecksum = true;
                            break;
                    }
                }
            }
            else
            {
                int diff = MAGIC.Length + command.Length + sizeof(UInt32);
                if (includeChecksum)
                {
                    if (index < diff + sizeof(UInt32))
                    {
                        int checksumIndex = index - diff;
                        checksum[checksumIndex] = b;
                        index++;
                        if (checksumIndex == sizeof(UInt32) - 1)
                        {
                            Checksum = BitConverter.ToUInt32(checksum, 0);
                        }
                    }
                    else if (index - (diff + sizeof(UInt32)) < Length)
                    {
                        int payloadIndex = index - (diff + sizeof(UInt32));
                        Payload[payloadIndex] = b;
                        if (payloadIndex == Length - 1)
                        {
                            // Finished!
                            if (NewMessage != null)
                            {
                                NewMessage(Command, Payload);
                            }
                            index = 0;  // re-start
                        }
                        else
                        {
                            index++;
                        }
                    }
                    else
                    {
                        throw new Exception("Error");
                    }
                }
                else
                {
                    //if (index - (MAGIC.Length + command.Length + sizeof(UInt32)) < Length)
                    if (index - diff < Length)
                    {
                        //int payloadIndex = index - (MAGIC.Length + command.Length + sizeof(UInt32));
                        int payloadIndex = index - diff;
                        Payload[payloadIndex] = b;
                        if (payloadIndex == Length - 1)
                        {
                            // Finished!
                            if (NewMessage != null)
                            {
                                NewMessage(Command, Payload);
                            }
                            index = 0;  // re-start
                        }
                        else
                        {
                            index++;
                        }
                    }
                    else
                    {
                        throw new Exception("Error");
                    }
                }
            }
        }

        // TODO: add sending members

        // TODO: handle events internally? 
    }

    public abstract class ProtocolValue
    {
        public abstract byte[] GetBytes();
    }

    public class Protocol_UInt32 : ProtocolValue
    {
        public UInt32 Value { get; set; }

        public override byte[] GetBytes()
        {
            return BitConverter.GetBytes(Value);
        }
    }

    public class VersionTest : ProtocolMessage
    {
        [ProtocolMember]
        public UInt32 Timestamp;

        [ProtocolMember]
        public UInt32 Timestamp2 { get; set; }
    }

    public class Bitcoin_header : ProtocolMessage
    {
        [ProtocolMember(Order = 1)]
        public byte[] Magic = new byte[] { 0xFA, 0xBF, 0xB5, 0xDA };   // testnet

        [ProtocolMember(Order = 2)]
        public byte[] command = new byte[12];

        public string Command
        {
            get
            {
                string cmd = Encoding.ASCII.GetString(command);
                int index = cmd.IndexOf((char)0);
                if (index != -1)
                {
                    cmd = cmd.Substring(0, index);
                }
                return cmd;
            }
            set
            {
                byte[] bytes = Encoding.ASCII.GetBytes(value);
                command = new byte[12];
                bytes.CopyTo(command, 0);
            }
        }

        [ProtocolMember(Order = 3)]
        public UInt32 PayloadLength = 0;
    }

    public class Bitcoin_header_checksum : Bitcoin_header
    {
        /// <summary>
        /// First 4 bytes of sha256(sha256(payload)) (not included in version or verack) 
        /// </summary>
        [ProtocolMember(Order = 5)]
        public UInt32 Checksum
        {
            get
            {
                var sha256 = new SHA256Managed();
                var hash = sha256.ComputeHash(sha256.ComputeHash(Payload));
                //byte[] hash_reverse = new byte[] { hash[3], hash[2], hash[1], hash[0] };
                //return BitConverter.ToUInt32(hash_reverse, 0);
                return BitConverter.ToUInt32(hash, 0);
            }
            set
            {
                // TODO: save and compare?
            }
        }

        public byte[] Payload = new byte[] { };
    }

    public class Bitcoin_checksum : ProtocolMessage
    {
        /// <summary>
        /// First 4 bytes of sha256(sha256(payload)) (not included in version or verack) 
        /// </summary>
        [ProtocolMember(Order = 5)]
        public UInt32 Checksum
        {
            get
            {
                var sha256 = new SHA256Managed();
                var hash = sha256.ComputeHash(sha256.ComputeHash(Payload));
                //byte[] hash_reverse = new byte[] { hash[3], hash[2], hash[1], hash[0] };
                //return BitConverter.ToUInt32(hash_reverse, 0);
                return BitConverter.ToUInt32(hash, 0);
            }
            set
            {
                // TODO: save and compare?
            }
        }

        public byte[] Payload = new byte[] { };
    }

    public class Bitcoin_verack : ProtocolMessage
    {
        public Bitcoin_verack()
        {
            Header.Command = "verack";
        }

        [ProtocolMember]
        public Bitcoin_header Header = new Bitcoin_header();
    }

    public class Bitcoin_getdata : ProtocolMessage
    {
        public Bitcoin_getdata()
        {
            Count = new Bitcoin_var_int();
            Count.Changed += new Bitcoin_var_int.ChangedDelegate(Count_Changed);
        }

        void Count_Changed(ulong length)
        {
            Vectors = new Bitcoin_inv_vector[length];
            for (int n = 0; n < Vectors.Length; n++)
            {
                Vectors[n] = new Bitcoin_inv_vector();
            }
        }

        [ProtocolMember(Order = 10)]
        public Bitcoin_var_int Count;

        [ProtocolMember(Order = 11)]
        public Bitcoin_inv_vector[] Vectors = new Bitcoin_inv_vector[] { };
    }

    // TODO: NYI
    public class Bitcoin_getblocks : ProtocolMessage
    {
        public Bitcoin_getblocks()
        {
            StartCount.Changed += StartCount_Changed;
        }

        void StartCount_Changed(ulong length)
        {
            HashStart = new byte[length][];
            for (int n = 0; n < HashStart.Length; n++)
            {
                HashStart[n] = new byte[32];
            }
        }

        [ProtocolMember(Order = 10)]
        public UInt32 Version;

        [ProtocolMember(Order = 11)]
        public Bitcoin_var_int StartCount = new Bitcoin_var_int();

        [ProtocolMember(Order = 12)]
        public byte[][] HashStart;

        [ProtocolMember(Order = 13)]
        public byte[] HashStop = new byte[32];
    }

    public class Bitcoin_addr : ProtocolMessage
    {
        public Bitcoin_addr()
        {
        }

        [ProtocolMember(Order = 10)]
        public byte Count  // TODO: replace with var_int (1+) type
        {
            get
            {
                return (byte)AddressList.Length;
            }
            set
            {
                AddressList = new Bitcoin_TimestampNetworkAddress[value];
                for (int n = 0; n < AddressList.Length; n++)
                {
                    AddressList[n] = new Bitcoin_TimestampNetworkAddress();
                }
            }
        }
        //[ProtocolMember(Order = 11)]
        //public Bitcoin_Timestamp Timestamp = new Bitcoin_Timestamp();

        //[ProtocolMember(Order = 12)]
        //public NetworkAddress[] AddressList = new NetworkAddress[] { };

        [ProtocolMember(Order = 11)]
        public Bitcoin_TimestampNetworkAddress[] AddressList = new Bitcoin_TimestampNetworkAddress[] { };
    }

    public class Bitcoin_inv_vector : ProtocolMessage
    {
        [ProtocolMember(Order = 10)]
        public UInt32 type;
        [ProtocolMember(Order = 11)]
        public byte[] hash = new byte[32];

        public VectorType Type
        {
            get
            {
                return (VectorType)type;
            }
            set
            {
                type = (UInt32)value;
            }
        }

        public enum VectorType
        {
            ERROR = 0,
            MSG_TX = 1,
            MSG_BLOCK = 2
        }
    }

    public class Bitcoin_inv : ProtocolMessage
    {
        public Bitcoin_inv()
        {
            Count = new Bitcoin_var_int();
            Count.Changed += new Bitcoin_var_int.ChangedDelegate(Count_Changed);
        }

        void Count_Changed(ulong length)
        {
            Vectors = new Bitcoin_inv_vector[length];
            for (int n = 0; n < Vectors.Length; n++)
            {
                Vectors[n] = new Bitcoin_inv_vector();
            }
        }

        [ProtocolMember(Order = 10)]
        public Bitcoin_var_int Count;

        [ProtocolMember(Order = 11)]
        public Bitcoin_inv_vector[] Vectors = new Bitcoin_inv_vector[] { };
    }

    public class Bitcoin_TimestampNetworkAddress : ProtocolMessage
    {
        [ProtocolMember(Order = 1)]
        public UInt32 Timestamp = 0;    // TODO: what differs this timestamp from an UNIX 64 bit?

        [ProtocolMember(Order = 2)]
        public NetworkAddress AddressList = new NetworkAddress();
    }

    public class Bitcoin_Timestamp : ProtocolMessage
    {
        public DateTime Timestamp
        {
            get
            {
                TimeSpan span = TimeSpan.FromSeconds(timestamp);
                DateTime time = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime() + span;
                return time;
            }
            set
            {
                // Create Timespan by subtracting the value provided from the Unix Epoch
                TimeSpan span = (value - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());
                // return the total seconds (which is a UNIX timestamp)
                timestamp = (UInt32)span.TotalSeconds;
            }
        }

        [ProtocolMember]
        public UInt64 timestamp = 0;
    }

    public class Bitcoin_version : ProtocolMessage
    {
        public Bitcoin_version()
        {
            //Header.Command = "version";
            Nonce = (UInt64)(random.NextDouble() * UInt64.MaxValue);
            Services = 1;
            Timestamp = DateTime.UtcNow;
        }

        public DateTime Timestamp
        {
            get
            {
                TimeSpan span = TimeSpan.FromSeconds(timestamp);
                DateTime time = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime() + span;
                return time;
            }
            set
            {
                // Create Timespan by subtracting the value provided from the Unix Epoch
                TimeSpan span = (value - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());
                // return the total seconds (which is a UNIX timestamp)
                timestamp = (UInt32)span.TotalSeconds;
            }
        }

        // TODO: where is the header? combine ProtocolMessage with ProtocolHandler?
        //[ProtocolMember]
        //public Bitcoin_header Header = new Bitcoin_header();

        [ProtocolMember]
        public UInt32 Version = 32100;

        [ProtocolMember]
        public UInt64 Services;

        [ProtocolMember]
        public UInt64 timestamp;

        [ProtocolMember]
        public byte[] AddMe = new byte[26];

        [ProtocolMember]
        public byte[] AddYou = new byte[26];

        [ProtocolMember]
        public UInt64 Nonce;

        [ProtocolMember]
        public byte[] SubVersion = new byte[] { 0 };   // TODO: replace with string?

        [ProtocolMember]
        public UInt32 StartHeight = 0;  // TODO: how to change?
    }
    /// <summary>
    /// The block message is sent in response to a getdata message which requests 
    /// transaction information from a block hash. 
    /// 
    /// The SHA256 hash that identifies each block (and which must have a run of 0 bits) 
    /// is calculated from the first 6 fields of this structure (version, prev_block, 
    /// merkle_root, timestamp, bits, nonce, and standard SHA256 padding, making two 64-byte 
    /// chunks in all) and not from the complete block. To calculate the hash, only two 
    /// chunks need to be processed by the SHA256 algorithm. Since the nonce field is in 
    /// the second chunk, the first chunk stays constant during mining and therefore only 
    /// the second chunk needs to be processed. However, a Bitcoin hash is the hash of 
    /// the hash, so two SHA256 rounds are needed for each mining iteration.
    /// </summary>
    public class Bitcoin_block : ProtocolMessage
    {
        public Bitcoin_block()
        {
            txn_count = new Bitcoin_var_int();
            txn_count.Changed += new Bitcoin_var_int.ChangedDelegate(txn_count_Changed);
        }

        void txn_count_Changed(ulong length)
        {
            txns = new Bitcoin_tx[length];
            for (int n = 0; n < txns.Length; n++)
            {
                txns[n] = new Bitcoin_tx();
            }
        }

        [ProtocolMember(Order = 10)]
        public UInt32 Version;

        [ProtocolMember(Order = 11)]
        public byte[] PreviousBlock = new byte[32]; // TODO: change to previousBlock and add PreviousBlock get set which reverses array

        [ProtocolMember(Order = 12)]
        public char[] MerkleRoot = new char[32];    // TODO: make Hash type?

        [ProtocolMember(Order = 13)]
        public UInt32 timestamp;

        [ProtocolMember(Order = 14)]
        public UInt32 bits;

        [ProtocolMember(Order = 15)]
        public UInt32 nonce;

        [ProtocolMember(Order = 16)]
        public Bitcoin_var_int txn_count = new Bitcoin_var_int();

        [ProtocolMember(Order = 17)]
        public Bitcoin_tx[] txns;

        /// <summary>
        /// The SHA256 hash that identifies each block (and which must have a run of 0 bits) 
        /// is calculated from the first 6 fields of this structure (version, prev_block, 
        /// merkle_root, timestamp, bits, nonce, and standard SHA256 padding, making two 
        /// 64-byte chunks in all) and not from the complete block. To calculate the hash, 
        /// only two chunks need to be processed by the SHA256 algorithm. Since the nonce 
        /// field is in the second chunk, the first chunk stays constant during mining and 
        /// therefore only the second chunk needs to be processed. However, a Bitcoin hash 
        /// is the hash of the hash, so two SHA256 rounds are needed for each mining iteration.
        /// See: http://forum.bitcoin.org/index.php?topic=9047.msg174456#msg174456
        /// See: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
        /// </summary>
        public byte[] Hash
        {
            get
            {
                //byte[] payload = this.Pack();//.Reverse().ToArray();
                //byte[] padded = Enumerable.Repeat((byte)0, 128).ToArray();
                //Array.Copy(payload, padded, 80);  // from start to nonce
                //padded[80] = 0x80;
                //padded[126] = 0x02;
                //padded[127] = 0x80;

                //                byte[] packed = this.Pack();
                //                Bitcoin_block testBlock = new Bitcoin_block();
                //                testBlock.Unpack(packed);
                //                byte[] packed2 = testBlock.Pack();
                //// TODO: assert block => unblock gives initial data

                byte[] data = new byte[80];
                Array.Copy(this.Pack(), data, 80);
                var sha256 = new SHA256Managed();
                var hash = sha256.ComputeHash(sha256.ComputeHash(data)).Reverse().ToArray();
                return hash;
            }
        }
    }

    public class Bitcoin_tx : ProtocolMessage
    {
        public Bitcoin_tx()
        {
            tx_in_count = new Bitcoin_var_int();
            tx_in_count.Changed += new Bitcoin_var_int.ChangedDelegate(tx_in_count_Changed);
            tx_out_count = new Bitcoin_var_int();
            tx_out_count.Changed += new Bitcoin_var_int.ChangedDelegate(tx_out_count_Changed);
        }

        void tx_in_count_Changed(ulong length)
        {
            tx_in = new Bitcoin_tx_in[length];
            for (int n = 0; n < tx_in.Length; n++)
            {
                tx_in[n] = new Bitcoin_tx_in();
            }
        }

        void tx_out_count_Changed(ulong length)
        {
            tx_out = new Bitcoin_tx_out[length];
            for (int n = 0; n < tx_out.Length; n++)
            {
                tx_out[n] = new Bitcoin_tx_out();
            }
        }

        [ProtocolMember(Order = 10)]
        public UInt32 version;

        [ProtocolMember(Order = 11)]
        public Bitcoin_var_int tx_in_count;

        [ProtocolMember(Order = 12)]
        public Bitcoin_tx_in[] tx_in;

        [ProtocolMember(Order = 13)]
        public Bitcoin_var_int tx_out_count;

        [ProtocolMember(Order = 14)]
        public Bitcoin_tx_out[] tx_out;

        [ProtocolMember(Order = 15)]
        public UInt32 lock_time;
    }

    public class Bitcoin_tx_in : ProtocolMessage
    {
        public Bitcoin_tx_in()
        {
            script_length = new Bitcoin_var_int();
            script_length.Changed += script_length_Changed;
        }

        void script_length_Changed(ulong length)
        {
            signature_script = new byte[length];
        }

        [ProtocolMember(Order = 10)]
        public Bitcoin_OutPoint previous_output = new Bitcoin_OutPoint();

        [ProtocolMember(Order = 11)]
        public Bitcoin_var_int script_length;

        [ProtocolMember(Order = 12)]
        public byte[] signature_script;

        [ProtocolMember(Order = 13)]
        public UInt32 sequence;
    }

    public class Bitcoin_tx_out : ProtocolMessage
    {
        public Bitcoin_tx_out()
        {
            pk_script_length = new Bitcoin_var_int();
            pk_script_length.Changed += pk_script_length_Changed;
        }

        void pk_script_length_Changed(ulong length)
        {
            pk_script = new byte[length];
        }

        [ProtocolMember(Order = 10)]
        public UInt64 value;

        [ProtocolMember(Order = 11)]
        public Bitcoin_var_int pk_script_length;

        [ProtocolMember(Order = 12)]
        public byte[] pk_script;
    }

    public class Bitcoin_OutPoint : ProtocolMessage
    {
        [ProtocolMember(Order = 10)]
        public char[] hash = new char[32];

        [ProtocolMember(Order = 11)]
        public UInt32 index;
    }

    public class MessageVersion
    {
        public static byte[] testPacket = new byte[] 
        {
            0xFA,0xBF,0xB5,0xDA,
            //0xF9,0xBE,0xB4,0xD9,
            0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x00,0x00,0x00,0x00,0x00,
            0x55,0x00,0x00,0x00,0x9C,0x7C,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0xE6,0x15,0x10,0x4D,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x0A,0x00,0x00,0x01,
            0xDA,0xF6,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0xFF,0xFF,0x0A,0x00,0x00,0x02,0x20,0x8D,0xDD,0x9D,0x20,0x2C,
            0x3A,0xB4,0x57,0x13,0x00,0x55,0x81,0x01,0x00
        };

        [ProtocolMember]
        private UInt64 timestamp;

        [ProtocolMember]
        public UInt32 Version = 31900;

        public byte[] GetPayload(string recipientIpAddress)
        {
            List<byte> payload = new List<byte>();
            timestamp = MessageHeader.ConvertToTimestamp(DateTime.UtcNow);
            payload.AddRange(BitConverter.GetBytes(Version));
            payload.AddRange(BitConverter.GetBytes((UInt64)1)); // NODE_NETWORK services
            payload.AddRange(BitConverter.GetBytes(timestamp));
            payload.AddRange(new NetworkAddress().GetBytes());
            payload.AddRange(new NetworkAddress().GetBytes(recipientIpAddress));
            payload.AddRange(BitConverter.GetBytes(MessageHeader.CreateNonce()));
            payload.Add(0); // "" sub-version string (string is 0 bytes long)
            payload.AddRange(BitConverter.GetBytes((UInt32)0)); // The last block received by the emitting node  => TODO: add block counter to constructor or parameters?
            MessageHeader header = new MessageHeader();
            header.payload = payload.ToArray();
            header.Command = "version";
            byte[] headerBytes = header.GetShortHeader();
            payload.InsertRange(0, headerBytes);
            return payload.ToArray();
        }

        public DateTime Timestamp
        {
            get
            {
                TimeSpan span = TimeSpan.FromSeconds(timestamp);
                DateTime time = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime() + span;
                return time;
            }
            set
            {
                // Create Timespan by subtracting the value provided from the Unix Epoch
                TimeSpan span = (value - new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime());
                // return the total seconds (which is a UNIX timestamp)
                timestamp = (UInt32)span.TotalSeconds;
            }
        }
    }
}
